//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>

#include "i8042.h"
#include "ps2mouse.h"

ECode Mouse::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (bytesToRead >= MOUSEBUF || pBuffer == NULL
        || (uint32_t)pBuffer->GetCapacity()
        < (bytesToRead * sizeof(MouseData))
        || (u64Offset != 0)) {
        return E_INVALID_ARGUMENT;
    }

    if (0 == bytesToRead) {
        return NOERROR;
    }

    WaitResult wr;
    while ((Int32)((MOUSEBUF + m_uTailer - m_uHeader) % MOUSEBUF)
        < bytesToRead) {
        m_receiveEvent.Wait(&wr, NULL);
    }

    if (bytesToRead + m_uHeader <= MOUSEBUF) {
        pBuffer->Copy((Byte *)&m_uMouseData[m_uHeader],
                        bytesToRead * sizeof(uint32_t));

        m_uHeader += bytesToRead;
        assert(m_uHeader <= MOUSEBUF);
        if (MOUSEBUF == m_uHeader) {
            m_uHeader = 0;
        }
    }
    // Tailer < Header
    else {
        assert(m_uTailer < m_uHeader);
        uint32_t uTowPartRead;

        uint8_t uLeaveSize = MOUSEBUF - m_uHeader;
        pBuffer->Copy((Byte *)&m_uMouseData[m_uHeader],
                                        uLeaveSize * sizeof(uint32_t));
        uTowPartRead = bytesToRead - uLeaveSize;
        assert(uTowPartRead > 0);

        pBuffer->Append((Byte *)m_uMouseData, uTowPartRead * sizeof(uint32_t));
        m_uHeader = uTowPartRead;
    }

    return NOERROR;
}

ECode Mouse::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode Mouse::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

static void CDECL MouseIsr(
    irq_t irq, void *pvDevice, InterruptContext *pContext)
{
    Mouse       *pMouse = (Mouse *)pvDevice;
    MouseData   mouseData;

    assert(pMouse);

    // Sanity check
    //
    if (!MOUSE_RECEIVE_INTERRUPT(pMouse->m_uPortBase)) {
        return;
    }

    I8042RecvData(pMouse->m_uPortBase, &mouseData.status);
    I8042RecvData(pMouse->m_uPortBase, &mouseData.x);
    I8042RecvData(pMouse->m_uPortBase, &mouseData.y);
    if (pMouse->m_bWheel) {
        I8042RecvData(pMouse->m_uPortBase, &mouseData.reserve);
    }

    if (((pMouse->m_uTailer + 1) % MOUSEBUF) != pMouse->m_uHeader) {
        pMouse->m_uMouseData[pMouse->m_uTailer] = *(UInt32 *)&mouseData;
        pMouse->m_uTailer++;
        if (MOUSEBUF == pMouse->m_uTailer) {
            pMouse->m_uTailer = 0;
        }
    }

    pMouse->m_receiveEvent.NotifyByIsr();
}

ECode Mouse::Initialize()
{
    uint8_t uData;
    ECode     ec;

    m_uPortBase = 0x60;
    m_uIrq = 12;

    // receive any other data.
    //
    while (KBDHasData(m_uPortBase)) {
        if (I8042RecvData(m_uPortBase, &uData)) {
            return E_TIMED_OUT;
        }
    }

    // mouse interface test
    //
    if (I8042SendCommand(m_uPortBase, I8042_CMD_TEST_MOUSE)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (I8042RecvData(m_uPortBase, &uData)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    switch (uData) {
        case 0x01:
            kprintf("Clock line stuck low\n");
            ec = E_FAIL;
            goto Exit;
        case 0x02:
            kprintf("Clock line stuck high\n");
            ec = E_FAIL;
            goto Exit;
        case 0x03:
            kprintf("Data line stuck low\n");
            ec = E_FAIL;
            goto Exit;
        case 0x04:
            kprintf("Data line stuck high\n");
            ec = E_FAIL;
            goto Exit;
    }

    // enable mouse interface
    //
    if (I8042SendCommand(m_uPortBase, I8042_CMD_MOUSE_ENABLE)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }

    // mouse self-test
    if (SendData2Mouse(m_uPortBase, MS_CMD_RESET)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (I8042RecvData(m_uPortBase, &uData)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (CMD_REPLY_POR != uData) {
        kprintf("mouse self-test, return 0x%x\n", uData);
        ec = E_FAIL;
        goto Exit;
    }

    // check the mouse wheel mode
    //
    if (SendData2Mouse(m_uPortBase, MS_CMD_SETSAMPLERATE)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (SendData2Mouse(m_uPortBase, 200)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (SendData2Mouse(m_uPortBase, MS_CMD_SETSAMPLERATE)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (SendData2Mouse(m_uPortBase, 100)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (SendData2Mouse(m_uPortBase, MS_CMD_SETSAMPLERATE)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (SendData2Mouse(m_uPortBase, 80)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }

    if (SendData2Mouse(m_uPortBase, MS_CMD_GETID)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (I8042RecvData(m_uPortBase, &uData)) {
        ec = E_TIMED_OUT;
        goto Exit;
    }
    if (uData) {
        assert(0x03 == uData);
        m_bWheel = TRUE;
    }

    MouseEnableInterrupt(m_uPortBase);

    // Set mouse enable data reproting
    //
    if (SendData2Mouse(m_uPortBase, MS_CMD_ENABLE)) {
        kprintf("enable mouse, no ack\n");
        ec = E_FAIL;
        goto Exit;
    }

    DzRegisterIsr(12, IPL8, &MouseIsr, this);

    kprintf("mouse driver initialised OK!\n");

    ec = NOERROR;

Exit:
    if (FAILED(ec)) {
        kprintf("mouse driver initialised failed!\n");
        MouseDisableInterrupt(m_uPortBase);
    }

    return ec;
}

EXTERN IDeviceDriver * CDECL CreateMouse(uint_t uDeviceNo, void *pvParameter)
{
    Mouse *pMouse = new Mouse();
    if (NULL == pMouse) {
        kprintf("out of memory\n");
        return NULL;
    }

    ECode ec = pMouse->Initialize();
    if (FAILED(ec)) {
        kprintf("initialize mouse failed, the ec = 0x%08x\n", ec);
        delete pMouse;
        return NULL;
    }

    pMouse->AddRef();
    return pMouse;
}
